package weeny.recycle;

import java.awt.BasicStroke;
import java.awt.Font;
import java.awt.Stroke;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.reflect.Array;
import java.lang.reflect.Modifier;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import prefuse.data.Edge;
import prefuse.data.Graph;
import prefuse.data.Node;
import prefuse.data.Schema;
import prefuse.data.Table;
import prefuse.data.Tuple;
import prefuse.data.io.DataIOException;
import prefuse.util.FontLib;
import prefuse.util.StrokeLib;
import prefuse.util.io.XMLWriter;
import prefuse.visual.VisualItem;
import weeny.util.Counter;
import weeny.util.logs.Debug;

@Deprecated
class ObjectParserFactory{
	private static Map<Class, ObjectParser> parsers = new HashMap<Class, ObjectParser>();
	public static boolean contains(Class type){
		if (parsers.containsKey(type)) {
			return true;
		}
		else{
			for(Class c : parsers.keySet()){
				if (c.isAssignableFrom(type)) {
					Debug.info("ObjectParser: "+type+" <--> "+c);
					parsers.put(type, parsers.get(c));
					return true;
				}
			}
		}
		return false;
	}
	public static ObjectParser get(Class c){
		return parsers.get(c);
	}
	public static void put(Class c, ObjectParser p){
		if (contains(c)) {
			Debug.info("conflict: "+c);
		}
		parsers.put(c, p);
	}
    
	//helper functions	
	public static String makeCSVSafe(String s) {
        int q = -1;
        if ( (q=s.indexOf('\"')) >= 0 ||
             s.indexOf(',')  >= 0 || s.indexOf('\n') >= 0 ||
             Character.isWhitespace(s.charAt(0)) ||
             Character.isWhitespace(s.charAt(s.length()-1)) )
        {
            if ( q >= 0 ) s = s.replaceAll("\"", "\"\"");
            s = "\""+s+"\"";
        }
        return s;
    }
	public static String csvString(String[] strings){
		if (strings == null) {
			return null;
		}
		if (strings.length == 0) {
			return "";
		}
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < strings.length; i++) {
			if (i>0) sb.append(",");
			sb.append(makeCSVSafe(strings[i]));
		}
		return sb.toString();
	}
	public static String csvString(Collection coll){
		if (coll == null) {
			return null;
		}
		String[] strings = new String[coll.size()];
		int idx = 0;
		for(Object o : coll){
			strings[idx++] = o.toString();
		}
		return csvString(strings);
	}
    public static String array2String(Object array){
    	StringBuffer sb = new StringBuffer();
    	if (array.getClass() == String[].class) {
			sb.append(csvString((String[]) array));
		}
		else{
			
			int size = Array.getLength(array);
			for(int i = 0; i<size; i++){
				if (i > 0) sb.append(" ");
				sb.append(Array.get(array, i).toString());
			}
		}
		return sb.toString();
    }
    public static Object string2Array(String code, Class type){
    	if (type.equals(String.class)) {
			List<String> list = ArraySupportedGraphReader.csvStringList(code);
			String[] result = new String[list.size()];
			for(int i = 0; i<list.size(); i++){
				result[i] = list.get(i);
			}
			return result; 
		}
    	else{
    		String[] tokens = code.split(" ");
    		Object result = Array.newInstance(type, tokens.length);
    		for(int i = 0; i<tokens.length; i++){
    			if (type.equals(int.class)) {
					Array.setInt(result, i, Integer.parseInt(tokens[i]));
				}
    			else if (type.equals(float.class)){
    				Array.setFloat(result, i, Float.parseFloat(tokens[i]));
    			}
    			else if (type.equals(boolean.class)){
    				Array.setBoolean(result, i, Boolean.parseBoolean(tokens[i]));
    			}
    			else if (type.equals(double.class)){
    				Array.setDouble(result, i, Double.parseDouble(tokens[i]));
    			}
    			else if (type.equals(long.class)){
    				Array.setLong(result, i, Long.parseLong(tokens[i]));
    			}
    			else{
    				throw new IllegalStateException("unknow type: "+type);
    			}
    		}
    		return result;
    	}
    }
	
    //predefined parsers
    private static ObjectParser fontParser = new ObjectParser() {
		public String encode(Object o) {
			Font font = (Font) o;
			return font.getName()+","+font.getSize()+","+font.getStyle();
		}
		public Object decode(String s) {
			String[] tokens = s.split(",");
			int size  = Integer.parseInt(tokens[1]);
			int style = Integer.parseInt(tokens[2]);
			return FontLib.getFont(tokens[0], style, size);
		}
	};
    private static ObjectParser rectParser = new ObjectParser() {
		public String encode(Object o) {
			Rectangle2D rect = (Rectangle2D) o;
			return rect.getX()+" "+rect.getY()+" "+rect.getWidth()+" "+rect.getHeight();
		}
		
		public Object decode(String s) {
			double[] val = (double[]) string2Array(s, double.class);
			return new Rectangle2D.Double(val[0], val[1], val[2], val[3]);
		}
	};
    private static ObjectParser strokeParser = new ObjectParser() {
		public String encode(Object o) {
			return array2String(stroke2Array((BasicStroke) o));
		}
		public Object decode(String s) {
			return array2Stroke((float[]) string2Array(s, float.class));
		}
		public float[] stroke2Array(BasicStroke stk){
			float[] result = new float[5+ (stk.getDashArray()==null? 0 : stk.getDashArray().length)];
			result[0] = stk.getLineWidth();
			result[1] = stk.getDashPhase();
			result[2] = stk.getEndCap();
			result[3] = stk.getLineJoin();
			result[4] = stk.getMiterLimit();
			for(int i = 5; i<result.length; i++){
				result[i] = stk.getDashArray()[i-5];
			}
			return result;
		}
		public BasicStroke array2Stroke(float[] vec){
			float[] dashes = null;
			if (vec == null || vec.length == 0) {
				return StrokeLib.getStroke(1);
			}
			if (vec.length > 5) {
				dashes = new float[vec.length-5];
				for(int i = 0; i<dashes.length; i++){
					dashes[i] = vec[5+i];
				}
				return StrokeLib.getStroke(vec[0],(int)vec[2], (int)vec[3], (int)vec[4], dashes, vec[1]);
			}
			return StrokeLib.getStroke(vec[0]);
		}
	};
	static{
		parsers.put(Stroke.class, strokeParser);
		parsers.put(Rectangle2D.class, rectParser);
		parsers.put(Font.class, fontParser);
	}
}
@Deprecated
public class MoreGraphWriter extends ArraySupportedGraphWriter{
	
	public MoreGraphWriter() {
		super();
	}
	public MoreGraphWriter(Schema nodeSchme){
		super(nodeSchme);
	}
	public MoreGraphWriter(Schema nodeSchme, Schema edgeSchema){
		super(nodeSchme, edgeSchema);
	}
	public void safeWrite(Graph graph, String file){
		try {
			writeGraph(graph, file);
		} catch (DataIOException e) {
			e.printStackTrace();
		}
	}
	@Override
	public void writeGraph(Graph graph, OutputStream os) throws DataIOException {
		Schema ns = graph.getNodeTable().getSchema();
		Schema es = graph.getEdgeTable().getSchema();
		if (m_NodeSchemaToWrite == null) {
			m_NodeSchemaToWrite = new Schema();
			for(int i = 0; i<ns.getColumnCount(); i++){
				m_NodeSchemaToWrite.addColumn(ns.getColumnName(i), ns.getColumnType(i));
			}
		}
		if (m_EdgeSchemaToWrite == null) {
			m_EdgeSchemaToWrite = new Schema();
			for(int i = 0; i<es.getColumnCount(); i++){
				m_EdgeSchemaToWrite.addColumn(es.getColumnName(i), es.getColumnType(i));
			}
		}
		m_NodeSchemaToWrite = convertSchema(m_NodeSchemaToWrite, graph.getNodeTable());
		m_EdgeSchemaToWrite = convertSchema(m_EdgeSchemaToWrite, graph.getEdgeTable());
		
        XMLWriter xml = new XMLWriter(new PrintWriter(os));
        xml.begin(Tokens.GRAPHML_HEADER, 2);
        
        xml.comment("Generated at " + new Date(System.currentTimeMillis()));
        
     // print the graph schema
        printSchema(xml, Tokens.NODE, m_NodeSchemaToWrite, null);
        printSchema(xml, Tokens.EDGE, m_EdgeSchemaToWrite, new String[] {
            graph.getEdgeSourceField(), graph.getEdgeTargetField()
        });
        xml.println();
        
        // print graph contents
        xml.start(Tokens.GRAPH, Tokens.EDGEDEF,
            graph.isDirected() ? Tokens.DIRECTED : Tokens.UNDIRECTED);
        
        // print the nodes
        xml.comment("nodes");
        Iterator nodes = graph.nodes();
        while ( nodes.hasNext() ) {
            Node n = (Node)nodes.next();
            
            if ( m_NodeSchemaToWrite.getColumnCount() > 0 ) {
                xml.start(Tokens.NODE, Tokens.ID, String.valueOf(n.getRow()));
                for ( int i=0; i<m_NodeSchemaToWrite.getColumnCount(); ++i ) {
                    String field = m_NodeSchemaToWrite.getColumnName(i);
                    String content = content(n,field);
                    if(content.equals("null"))
                    	continue;
                    xml.contentTag(Tokens.DATA, Tokens.KEY, field, content);
                }
                xml.end();
            } else {
                xml.tag(Tokens.NODE, Tokens.ID, String.valueOf(n.getRow()));
            }
        }
        
        // add a blank line
        xml.println();
        
        // print the edges
        String[] attr = new String[]{Tokens.ID, Tokens.SOURCE, Tokens.TARGET};
        String[] vals = new String[3];
        
        xml.comment("edges");
        Iterator edges = graph.edges();
        while ( edges.hasNext() ) {
            Edge e = (Edge)edges.next();
            vals[0] = String.valueOf(e.getRow());
            vals[1] = String.valueOf(e.getSourceNode().getRow());
            vals[2] = String.valueOf(e.getTargetNode().getRow());
            
            if ( es.getColumnCount() > 2 ) {
                xml.start(Tokens.EDGE, attr, vals, 3);
                for ( int i=0; i<m_EdgeSchemaToWrite.getColumnCount(); ++i ) {
                    String field = m_EdgeSchemaToWrite.getColumnName(i);
                    if ( field.equals(graph.getEdgeSourceField()) ||
                         field.equals(graph.getEdgeTargetField()) )
                        continue;
                    String content = content(e,field);
                    if (content.equals("null"))
						continue;
                    xml.contentTag(Tokens.DATA, Tokens.KEY, field, content);
                }
                xml.end();
            } else {
                xml.tag(Tokens.EDGE, attr, vals, 3);
            }
        }
        xml.end();
        
        // finish writing file
        xml.finish("</"+Tokens.GRAPHML+">\n");
        try {
			os.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

    protected String content(Tuple n, String field){
    	StringBuffer sb = new StringBuffer();
    	if (n.get(field)==null) {
			return "null";
		}
    	if (n.canGet(field, Collection.class)){
    		Collection collection = (Collection) n.get(field);
    		if (collection.isEmpty()) {
				return "";
			}
    		Object example = collection.iterator().next();
//    		if (example.getClass().equals(String.class)) {
//				sb.append(csvString(collection));
//				return sb.toString();
//			}
    		DecimalFormat formater = null;
    		if (example.getClass() == Boolean.class || example.getClass() == Long.class ||
    			example.getClass() == Integer.class ) {
				for(Object object : collection){
					sb.append(object+" ");
				}
				return sb.substring(0, sb.length()-1);
			}
    		else if(example.getClass() == Float.class){
    			formater = floatFormat;
    			for (Object object : collection) {
    				formater.format(object, sb, fp);
    				sb.append(" ");
    			}
        		return sb.substring(0, sb.length()-1);
    		}
    		else if(example.getClass() == Double.class){
    			formater = floatFormat;
    			for (Object object : collection) {
    				formater.format(object, sb, fp);
    				sb.append(" ");
    			}
        		return sb.substring(0, sb.length()-1);
    		}
    		else{
    			String[] strings = new String[collection.size()];
    			int idx = 0;
    			for(Object o : collection){
    				if (o instanceof String){
    					strings[idx++] = (String) o;
    				}
    				else if (o instanceof ObjectParser) {
    					strings[idx++] = ((ObjectParser) o).encode(o);	
					}
    				else {
    					strings[idx++] = ObjectParserFactory.get(o.getClass()).encode(o);
    				}
    			}
    			sb.append(csvString(strings));
				return sb.toString();
    		}
    	}
    	Object obj = n.get(field);
    	if (obj instanceof ObjectParser) {
			return ((ObjectParser)obj).encode(obj);
		}
    	if (ObjectParserFactory.contains(obj.getClass())){
    		return ObjectParserFactory.get(obj.getClass()).encode(obj);
    	}
    	if (TYPES.get(n.getColumnType(field)) == null) {
			throw new IllegalArgumentException("if it gets here, type should be in TYPES.");
		}
    	try {
    		Object array = n.get(field);
    		if (array.getClass() == String[].class) {
				sb.append(csvString((String[]) array));
			}
    		else{
    			int size = Array.getLength(array);
    			for(int i = 0; i<size; i++){
    				if (i > 0) sb.append(" ");
    				sb.append(Array.get(array, i).toString());
    			}
    		}
			return sb.toString();
		} catch (IllegalArgumentException e) {
			//if it is not a array, should be primitive
			return n.getString(field);
		}
    }
    
	protected void printSchema(XMLWriter xml, String group, Schema s, String[] ignore)
	{
		String[] attr = new String[] {Tokens.ID, Tokens.FOR, Tokens.ATTRNAME, Tokens.ATTRTYPE };
		String[] vals = new String[4];

		OUTER:
			for ( int i=0; i<s.getColumnCount(); ++i ) {
				vals[0] = s.getColumnName(i);

				for ( int j=0; ignore!=null && j<ignore.length; ++j ) {
					if ( vals[0].equals(ignore[j]) )
						continue OUTER;
				}

				vals[1] = group;
				vals[2] = vals[0];
				vals[3] = (String)TYPES.get(s.getColumnType(i));
				if (vals[3] == null) {
					if (replace.containsKey((s.getColumnName(i)))) {
						vals[3] = (String) TYPES.get(replace.get(s.getColumnName(i)));	
					}
				}
				if (vals[3] == null) {
					vals[3] = className(s.getColumnType(i));
				}
				if (field2ElementType.containsKey(vals[0])) {
					vals[3] +=":"+className(field2ElementType.get(vals[0]));
				}
				Object dflt = s.getDefault(i);

				if ( dflt == null ) {
					xml.tag(Tokens.KEY, attr, vals, 4);
				} else {
					xml.start(Tokens.KEY, attr, vals, 4);
					xml.contentTag(Tokens.DEFAULT, dflt.toString());
					xml.end();
				}
			}
	}
	protected String className(Class c){
		return c.toString().split(" ")[1];
	}
	public void setCoder(Class type, ObjectParser coder){
		ObjectParserFactory.put(type, coder);
	}
	private Map<String, Class> field2ElementType = new HashMap<String, Class>(); 
	protected Schema convertSchema(Schema schema, Table table){
		Schema resultSchema = new Schema();
		for(int i = 0; i<schema.getColumnCount(); i++){
			String field = schema.getColumnName(i);
			if (table.getColumn(field) == null) {
				throw new IllegalArgumentException("schema is bigger than table");
			}
			else if (TYPES.containsKey(schema.getColumnType(field))) {
				//if the type in schema is ok
				resultSchema.addColumn(field, schema.getColumnType(field));
			}
			else if (TYPES.containsKey(table.getColumnType(field))){
				//if the Class type in the schema is not good,
				//check the type in the table,
				//if the type in the table is ok
				resultSchema.addColumn(field, table.getColumnType(field));
			}
			else if (Collection.class.isAssignableFrom(table.getColumnType(field))) {
				//if the type in the table can be converted to array
				resultSchema.addColumn(field, getArrayClassForCollection(table, field));
			}
			else if (ObjectParser.class.isAssignableFrom(table.getColumnType(field))){
				resultSchema.addColumn(field, table.getColumnType(field));
			}
			else if (ObjectParserFactory.contains(table.getColumnType(field))){
				resultSchema.addColumn(field, table.getColumnType(field));
			}
			else {
				throw new IllegalStateException(field+" is not a good field (e.g., primative type or collection)");
			}
		}
		return resultSchema;
	}
	
	public static Class elementType(Table table, String field){
		for(Iterator<Tuple> it = table.tuples(); it.hasNext();){
			Collection coll = (Collection) it.next().get(field);
			if (coll == null || coll.isEmpty()) {
				continue;
			}
			else{
				Object elem = coll.iterator().next();
				if (elem instanceof Number || elem instanceof String || 
						elem instanceof ObjectParser || ObjectParserFactory.contains(elem.getClass())) {
					return elem.getClass();
				}
				else
					throw new IllegalArgumentException("cannot deal with this elem type: "+elem.getClass());
			}
		}
		//every tuple in the table has empty or null element in this field,
		//so we do not know that the element type is, just get the collection type
		return null;
	}
	protected Class getArrayClassForCollection(Table table, String field){
		Class result = table.getColumnType(field);
		if (Modifier.isAbstract(result.getModifiers()) || Modifier.isInterface(result.getModifiers())) {
			if (!replace.containsKey(field)) {
				throw new IllegalStateException("should set a replacement of concrete collection");
			}
			result = replace.get(field);
		}
		Class elemType = elementType(table, field);
		if (elemType != null){
			field2ElementType.put(field, elemType);
		}
		return result;
	}
	public static void main(String[] args) throws ClassNotFoundException {
		Graph g = new Graph();
		g.getNodeTable().addColumn("abc", Counter.class);
		g.getNodeTable().addColumn(VisualItem.STROKE, BasicStroke.class);
		Counter cc = new Counter();
		cc.increase("Acd");
		cc.increase("ww",10.3);
		g.addNode().set("abc", cc);
		List<Counter> list = new ArrayList<Counter>();
		list.add(cc);
		list.add(cc);
		g.getNodeTable().addColumn("listABC", ArrayList.class);
		g.addNode().set("listABC", list); 
		g.addNode().set(VisualItem.STROKE, StrokeLib.getStroke(2.3f));
		g.getNodeTable().addColumn("setSTK", HashSet.class);
		g.addNode().set("setSTK", new HashSet());
		Set<BasicStroke> stkSet = new HashSet<BasicStroke>();
		stkSet.add(StrokeLib.getStroke(22));
		stkSet.add(StrokeLib.getStroke(21));
		g.addNode().set("setSTK", stkSet);
		g.getEdgeTable().addColumn("intSet", HashSet.class);
		Set set = new HashSet();
		set.add("A");
		set.add("B");
		g.getEdge(g.addEdge(0, 2)).set("intSet", set);
		try {
			new MoreGraphWriter().writeGraph(g, Control.desktopFolder()+"ttt.xml");
		} catch (DataIOException e) {
			e.printStackTrace();
		}
	}
}